Skip to content

[中文版本](/posts/c教程/zh-CN/08-调试测试与 profiling/073-Assertions and Defensive Programming)

Bugs are inevitable, but crashes don’t have to be. C gives you direct power over the machine, which means you must protect your own assumptions. That’s where assertions and defensive programming come in: they help you catch mistakes early, fail fast, and make your code predictable.

Step 1. What Is an Assertion? ​

An assertion is a sanity check built into your code. It tests whether something you believe to be true actually is. If not, the program immediately stops with an error message, before things get worse.

Include the header:

#include <assert.h>

Example:

int divide(int a, int b) {
    assert(b != 0);  // ensure no division by zero
    return a / b;
}

Ifb is zero, the program aborts:

Assertion failed: (b != 0), function divide, file main.c, line 3.

Step 2. How Assertions Work ​

assert(expression) expands to something like:

if (!(expression)) {
    fprintf(stderr, "Assertion failed: %s, file %s, line %d\n",
            "expression", __FILE__, __LINE__);
    abort();
}

When compiled normally, it checks the condition. When compiled with-DNDEBUG, assertions are disabled.

Step 3. Enabling or Disabling Assertions ​

Default: assertions are active. To disable them (for production builds):

gcc -DNDEBUG program.c -o program

In that build,assert() statements are removed.

This lets you keep your debug checks without slowing down release binaries.

Step 4. Practical Example ​

#include <assert.h>
#include <stdio.h>
int find_max(int *arr, int n) {
    assert(arr != NULL);
    assert(n > 0);
    int max = arr[0];
    for (int i = 1; i < n; i++)
        if (arr[i] > max) max = arr[i];
    return max;
}
int main(void) {
    int data[] = {3, 5, 7, 2, 8};
    printf("Max: %d\n", find_max(data, 5));
}

If you pass aNULL pointer or invalid length, the program fails immediately.

Step 5. Writing Good Assertions ​

Assertions should test internal assumptions, not user input. Bad:

assert(argc == 3);

Good:

if (argc != 3) {
    fprintf(stderr, "Usage: %s input output\n", argv[0]);
    return 1;
}

Use assertions to check invariants inside your logic, things that should never happen unless there’s a bug.

Step 6. Defensive Programming Techniques ​

Defensive programming goes beyond assertions, it’s about writing code that assumes mistakes will happen.

Check every function return value:

FILE *f = fopen("file.txt", "r");
if (!f) {
    perror("fopen");
    return 1;
}

Validate inputs:

int divide(int a, int b) {
    if (b == 0) {
        fprintf(stderr, "Division by zero!\n");
        return 0;
    }
    return a / b;
}

Avoid undefined behavior:

  • Initialize all variables.
  • Don’t access freed memory.
  • Check array bounds.
  • Always matchmalloc() withfree().

Step 7. Assertions in Complex Systems ​

In large programs, assertions act like tripwires to detect when state becomes inconsistent.

Example: a queue

#include <assert.h>
void enqueue(int *queue, int *count, int value, int max) {
    assert(*count < max);
    queue[*count] = value;
    (*count)++;
}

If something goes wrong in your logic, the assertion will tell you immediately, before memory corruption happens.

Step 8. Logging vs Assertions ​

  • Assertions: catch programming errors.
  • Logging: record runtime information.

Combine both:

#include <assert.h>
#include <stdio.h>
int read_value(int *arr, int n, int index) {
    assert(index >= 0 && index < n);
    printf("Reading arr[%d] = %d\n", index, arr[index]);
    return arr[index];
}

Step 9. Using static_assert for Compile-Time Checks (C11+) ​

C11 introduced_Static_assert, which checks conditions during compilation.

#include <assert.h>
_Static_assert(sizeof(int) == 4, "int must be 4 bytes");

If the condition fails, the compiler stops with:

error: static assertion failed: "int must be 4 bytes"

This is perfect for configuration or architecture assumptions.

Step 10. Tiny Code: Safe Array Access ​

#include <assert.h>
#include <stdio.h>
#define MAX 5
int safe_get(int arr[], int n, int i) {
    assert(i >= 0 && i < n);
    return arr[i];
}
int main(void) {
    int nums[MAX] = {1, 2, 3, 4, 5};
    printf("%d\n", safe_get(nums, MAX, 2));  // OK
    printf("%d\n", safe_get(nums, MAX, 10)); // triggers assertion
}

Output:

Assertion failed: (i >= 0 && i < n), function safe_get, file main.c, line 7.

Why It Matters ​

Assertions and defensive coding make your software:

  • Safer, catch bugs early.
  • Easier to debug, fail at the source, not later.
  • More maintainable, documents assumptions clearly.

In C, a single bad pointer can crash your system. Assertions are your safety net.

Try It Yourself ​

  1. Add assertions to your stack or linked list implementation.
  2. Write a function that validates all arguments before proceeding.
  3. Use_Static_assert to check type sizes in a cross-platform program.
  4. Combine assertions with logging for detailed error reports.
  5. Run your program with invalid input and see how quickly assertions detect issues.

Next, you’ll move into unit testing in C, building small, automated tests to ensure every function works exactly as intended.

本站总访问 — 次